﻿using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Reflection;
using System.Text;
using System.Web.Services.Description;
using Microsoft.CSharp;

namespace Job.Core.Web
{
    /// <summary>
    /// WebService 代理操作。
    /// </summary>
    public static class WebServiceAgent
    {
        #region Properties

        private static Dictionary<string, Type> proxyTypes = new Dictionary<string, Type>();

        #endregion

        #region Methods

        /// <summary>
        /// 动态调用 web 服务。
        /// </summary>
        /// <param name="wsUrl">WebService 地址。</param>
        /// <param name="methodName">方法名。</param>
        /// <param name="args">调用参数。</param>
        /// <returns>调用返回值。</returns>
        public static object InvokeWebService(string wsUrl, string methodName, params object[] args)
        {
            return InvokeWebService(wsUrl, null, methodName, args);
        }

        /// <summary>
        /// 动态调用 web 服务。
        /// </summary>
        /// <param name="wsUrl">WebService 地址。</param>
        /// <param name="className">类名。</param>
        /// <param name="methodName">调用参数。</param>
        /// <param name="args">调用参数。</param>
        /// <returns>调用返回值。</returns>
        public static object InvokeWebService(string wsUrl, string className, string methodName, params object[] args)
        {
            if (string.IsNullOrEmpty(wsUrl))
                throw new ArgumentNullException("wsUrl");

            if (string.IsNullOrEmpty(methodName))
                throw new ArgumentNullException("methodName");

            Type proxyType = GetWsProxyType(wsUrl, className);
            object obj = Activator.CreateInstance(proxyType);
            MethodInfo mi = proxyType.GetMethod(methodName);

            return mi.Invoke(obj, args);
        }

        /// <summary>
        /// 获取目标 WebService 代理类型。
        /// </summary>
        /// <param name="wsUrl">目标 WebService 的 url。</param>
        /// <param name="className">WebService 的类名，如不需指定，则传入 null。</param>
        /// <returns>代理类型。</returns>
        public static Type GetWsProxyType(string wsUrl, string className)
        {
            if (string.IsNullOrEmpty(wsUrl))
                throw new ArgumentNullException("wsUrl");

            string nameSpance = "Job.Core.Web.DynamicWebCaller";
            if (string.IsNullOrEmpty(className))
            {
                className = wsUrl.Substring(wsUrl.LastIndexOf('/') + 1);
                int index = className.IndexOf('.');
                if (index != -1)
                    className = className.Substring(0, index);
            }
            
            string cacheKey = string.Format("{0}@{1}", wsUrl, className);
            if (proxyTypes.ContainsKey(cacheKey))
                return proxyTypes[cacheKey];

            //获取WSDL
            WebClient client = new WebClient();
            Stream stream = client.OpenRead(wsUrl + "?WSDL");
            ServiceDescription description = ServiceDescription.Read(stream);
            ServiceDescriptionImporter importer = new ServiceDescriptionImporter();
            importer.AddServiceDescription(description, string.Empty, string.Empty);

            //动态生成代理类            
            CodeNamespace cn = new CodeNamespace(nameSpance);
            CodeCompileUnit ccu = new CodeCompileUnit();
            ccu.Namespaces.Add(cn);
            importer.Import(cn, ccu);
            CSharpCodeProvider csc = new CSharpCodeProvider();            
            CompilerParameters cps = new CompilerParameters();
            cps.GenerateExecutable = false;
            cps.GenerateInMemory = true;
            cps.ReferencedAssemblies.Add("System.dll");
            cps.ReferencedAssemblies.Add("System.XML.dll");
            cps.ReferencedAssemblies.Add("System.Web.Services.dll");
            cps.ReferencedAssemblies.Add("System.Data.dll");

            //编译
            CompilerResults cr = csc.CompileAssemblyFromDom(cps, ccu);
            if (cr.Errors.HasErrors == true)
            {
                StringBuilder sb = new StringBuilder();
                foreach (CompilerError ce in cr.Errors)
                {
                    sb.Append(ce.ToString());
                    sb.Append(System.Environment.NewLine);
                }

                throw new Exception(sb.ToString());
            }

            //生成代理实例，并调用方法
            Assembly assembly = cr.CompiledAssembly;
            Type wsProxyType = assembly.GetType(nameSpance + "." + className, true, true);

            lock (proxyTypes)
            {
                if (!proxyTypes.ContainsKey(cacheKey))
                {
                    proxyTypes.Add(cacheKey, wsProxyType);
                }
            }

            return wsProxyType;
        }

        #endregion
    }
}
